package compiler.code_generator;

import compiler.semantics.SemanticAnalyzer;
import compiler.semantics.SymbolicTable;
import compiler.semantics.VarType;
import compiler.syntax_tree.*;

public class CodeGenerator
{
    private MinijavaFilePattern codeFilePattern;
    private SymbolicTable symbolicTable;

    public CodeGenerator(MinijavaFilePattern filePattern)
    {
        this.codeFilePattern = filePattern;
        this.symbolicTable = SemanticAnalyzer.symbolicTable;
    }

    public String generateCodeFileContents(Program program)
    {
        MainClass mainClass = program.mainClass;
        String className = mainClass.id1.name;
        String argName = mainClass.id2.name;
        String programContents = generateMemoryDirectives();
        programContents += generateStatementSequence(mainClass.statement);
        return codeFilePattern.produceMainEntry(className, argName, programContents);
    }

    private String generateMemoryDirectives()
    {
        String memoryDirectiveString = "";
        if (symbolicTable.size() > 0)
        {
            memoryDirectiveString = codeFilePattern.produceLocalsLimit(symbolicTable.size());
        }
        return memoryDirectiveString;
    }

    private String generateStatementSequence(Statement statementSequenceNode)
    {
        String statementsString = "";
        if (statementSequenceNode instanceof Block)
        {
            StatementList statementList = ((Block) statementSequenceNode).statementList;
            for (int i = 0; i < statementList.size(); i++)
            {
                Statement statementNode = statementList.elementAt(i);
                statementsString += generateStatement(statementNode);
            }
        }

        return statementsString;
    }

    private String generateStatement(Statement statement)
    {
        String statementStr = "";
        if (statement instanceof Print)
        {
            statementStr = generatePrint((Print) statement);
        }
        else if (statement instanceof Assignment)
        {
            Assignment assignment = (Assignment) statement;
            if (assignment.expression instanceof NewArray)
            {
                statementStr = generateNewArrayAssignment(assignment);
            }
            else
            {
                statementStr = generateAssignment(assignment);
            }
        }
        else if (statement instanceof ArrayAssign)
        {
            statementStr = generateArrayElementAssignment((ArrayAssign) statement);
        }
        else if (statement instanceof If)
        {
            statementStr = generateIfStatement((If) statement);
        }
        else if (statement instanceof While)
        {
            statementStr = generateWhileStatement((While) statement);
        }
        else if (statement instanceof Block)
        {
            statementStr = generateStatementSequence(statement);
        }
        return statementStr;
    }

    private String generateWhileStatement(While whileStatement)
    {
        String conditionStr = generateExpression(whileStatement.condition);
        String statementStr = generateStatement(whileStatement.statement);
        return codeFilePattern.produceWhileStatement(conditionStr, statementStr);
    }

    private String generateIfStatement(If ifStatement)
    {
        String conditionStr = generateExpression(ifStatement.condition);
        String ifStatementStr = generateStatement(ifStatement.ifStatement);
        String elseStatementStr = (ifStatement.elseStatement != null) ? generateStatement(ifStatement.elseStatement) : "";

        return codeFilePattern.produceIfStatement(conditionStr, ifStatementStr, elseStatementStr);
    }

    private String generatePrint(Print printStatement)
    {
        String statementStr = "";
        Expression expr = printStatement.expression;
        String outputParam = generateExpression(expr);
        if (SemanticAnalyzer.isArithmeticExpression(expr))
        {
            statementStr = codeFilePattern.printInt(outputParam);
        }
        else if (SemanticAnalyzer.isLogicalExpression(expr))
        {
            statementStr = codeFilePattern.printBool(outputParam);
        }
        return statementStr;
    }

    private String generateAssignment(Assignment assignment)
    {
        String statementStr = generateExpression(assignment.expression);
        String varName = assignment.id.name;
        Integer varAddress = symbolicTable.getSymbolAddress(varName);
        VarType varType = symbolicTable.getSymbolType(varName);
        statementStr += (varType == VarType.INT_ARRAY)
                ? codeFilePattern.storeRef(varAddress)
                : codeFilePattern.storeInt(varAddress);
        return statementStr;
    }

    private String generateNewArrayAssignment(Assignment assignment)
    {
        NewArray newArrayExpr = (NewArray) assignment.expression;
        Expression arraySizeExpression = newArrayExpr.expression;
        String statementStr = generateExpression(arraySizeExpression);
        statementStr += codeFilePattern.newIntArray();
        String varName = assignment.id.name;
        Integer varAddress = symbolicTable.getSymbolAddress(varName);
        statementStr += codeFilePattern.storeRef(varAddress);
        return statementStr;
    }

    private String generateArrayElementAssignment(ArrayAssign arrayAssign)
    {
        String varName = arrayAssign.id.name;
        Integer arrayAddress = symbolicTable.getSymbolAddress(varName);
        String statementStr = codeFilePattern.loadRef(arrayAddress);
        statementStr += generateExpression(arrayAssign.indexExpression);
        statementStr += generateExpression(arrayAssign.assignedExpression);
        statementStr += codeFilePattern.storeIntIntoArray();
        return statementStr;
    }

    private String generateArrayLookup(ArrayLookup arrayLookup)
    {
        String varName = arrayLookup.id.name;
        Integer arrayAddress = symbolicTable.getSymbolAddress(varName);
        String statementStr = codeFilePattern.loadRef(arrayAddress);
        statementStr += generateExpression(arrayLookup.indexExpression);
        statementStr += codeFilePattern.loadIntFromArray();
        return statementStr;
    }

    private String generateExpression(Expression expression)
    {
        String result = "";
        if (expression instanceof IntegerLiteral)
        {
            Integer literalValue = ((IntegerLiteral) expression).value;
            result = codeFilePattern.pushInt(literalValue);
        }
        else if (expression instanceof ArrayLookup)
        {
            result = generateArrayLookup((ArrayLookup) expression);
        }
        else if (expression instanceof False)
        {
            result = codeFilePattern.pushByte(0);
        }
        else if (expression instanceof True)
        {
            result = codeFilePattern.pushByte(1);
        }
        else if (expression instanceof And)
        {
            result = generateExpression(((And) expression).op1);
            result += generateExpression(((And) expression).op2);
            result += codeFilePattern.and();
        }
        else if (expression instanceof Or)
        {
            result = generateExpression(((Or) expression).op1);
            result += generateExpression(((Or) expression).op2);
            result += codeFilePattern.or();
        }
        else if (expression instanceof Not)
        {
            result = generateExpression(((Not) expression).expression);
            result += codeFilePattern.not();
        }
        else if (expression instanceof LessThan)
        {
            result = generateExpression(((LessThan) expression).op1);
            result += generateExpression(((LessThan) expression).op2);
            result += codeFilePattern.produceLessThan();
        }
        else if (expression instanceof Identifier)
        {
            String varName = ((Identifier) expression).name;
            VarType varType = symbolicTable.getSymbolType(varName);
            Integer varAddress = symbolicTable.getSymbolAddress(varName);
            result = (varType == VarType.INT_ARRAY)
                    ? codeFilePattern.loadRef(varAddress)
                    : codeFilePattern.loadInt(varAddress);
        }
        else if (expression instanceof Plus)
        {
            result = generateExpression(((Plus) expression).op1);
            result += generateExpression(((Plus) expression).op2);
            result += codeFilePattern.add();
        }
        else if (expression instanceof Minus)
        {
            result = generateExpression(((Minus) expression).op1);
            result += generateExpression(((Minus) expression).op2);
            result += codeFilePattern.sub();
        }
        else if (expression instanceof Multiply)
        {
            result = generateExpression(((Multiply) expression).op1);
            result += generateExpression(((Multiply) expression).op2);
            result += codeFilePattern.mul();
        }
        else if (expression instanceof Div)
        {
            result = generateExpression(((Div) expression).op1);
            result += generateExpression(((Div) expression).op2);
            result += codeFilePattern.div();
        }
        else if (expression instanceof Mod)
        {
            result = generateExpression(((Mod) expression).op1);
            result += generateExpression(((Mod) expression).op2);
            result += codeFilePattern.mod();
        }
        else if (expression instanceof UnaryMinus)
        {
            result = generateExpression(((UnaryMinus) expression).expression);
            result += codeFilePattern.negate();
        }
        return result;
    }

}
